๐ŸŒ Socket UDP in Python

Guida Completa alla Programmazione di Rete con Protocollo UDP

๐Ÿ“‹ 1. Introduzione alle Socket

1.1 Cos'รจ una Socket?

Una socket รจ un'interfaccia di programmazione che permette la comunicazione tra processi su una rete. Possiamo pensarla come una "presa" virtuale attraverso cui i dati fluiscono tra due programmi, anche se si trovano su computer diversi.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ CLIENT โ”‚ โ†โ”€โ”€โ”€โ”€ Messaggio โ”€โ”€โ”€โ”€โ”€โ†’ โ”‚ SERVER โ”‚ โ”‚ (192.168.1.2) โ”‚ โ”‚ (192.168.1.1) โ”‚ โ”‚ Porta 5001 โ”‚ โ”‚ Porta 5000 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

1.2 Componenti di una Comunicazione

๐Ÿ’ก Elementi Necessari per la Comunicazione
  • Indirizzo IP: identifica univocamente la macchina sulla rete (es: 192.168.1.100, localhost)
  • Porta: identifica il processo/servizio specifico sulla macchina (numero da 0 a 65535)
  • Protocollo: regole di comunicazione che definiscono come i dati vengono trasmessi (TCP o UDP)
  • Famiglia di indirizzi: tipo di rete (IPv4, IPv6, etc.)

โš–๏ธ 2. TCP vs UDP: Le Differenze Fondamentali

2.1 Confronto Tecnico

Caratteristica TCP (Transmission Control Protocol) UDP (User Datagram Protocol)
Tipo di Connessione Orientato alla connessione (handshake) Senza connessione (connectionless)
Affidabilitร  Garantita: ritrasmissione automatica Non garantita: i pacchetti possono perdersi
Ordine dei Pacchetti Preservato sempre Non garantito
Velocitร  Piรน lento (overhead maggiore) Piรน veloce (overhead minimo)
Controllo di Flusso Presente (flow control) Assente
Controllo di Congestione Presente Assente
Dimensione Header 20 bytes (minimo) 8 bytes (fisso)
Uso Tipico Web (HTTP), Email, File Transfer Streaming, Gaming, DNS, VoIP

2.2 Analogia del Mondo Reale

๐Ÿ“ฌ TCP = Raccomandata con Ricevuta

  • Stabilisci una "connessione" con il destinatario
  • Sai che il messaggio รจ arrivato
  • Ricevi conferma di consegna
  • Se si perde, viene rispedito
  • Arriva nell'ordine corretto
  • Costa di piรน in tempo e risorse

๐Ÿ“ฎ UDP = Cartolina Normale

  • Nessuna connessione preventiva
  • Invii e speri che arrivi
  • Nessuna conferma di ricezione
  • Puรฒ perdersi per strada
  • Puรฒ arrivare in disordine
  • Veloce ed economica

2.3 Quando Usare UDP?

โœ… Usa UDP quando:
  • La velocitร  รจ prioritaria rispetto all'affidabilitร 
  • Perdere qualche pacchetto non รจ critico (es: streaming video in diretta)
  • Invii dati frequenti e aggiornati dove i dati vecchi non servono (posizione GPS, sensori)
  • Fai broadcast o multicast a piรน destinatari simultaneamente
  • Vuoi overhead minimo e massima performance
  • Implementi il tuo meccanismo di affidabilitร  personalizzato
โš ๏ธ Evita UDP quando:
  • Ogni dato รจ critico e non puรฒ essere perso (transazioni bancarie, operazioni su database)
  • L'ordine dei messaggi รจ essenziale per l'elaborazione
  • Devi garantire la consegna di tutti i pacchetti
  • Trasferisci file completi che devono arrivare integri

๐Ÿ—๏ธ 3. Architettura Client-Server

3.1 Modello di Comunicazione

๐Ÿ–ฅ๏ธ SERVER

1. Crea socket UDP
โ†“
2. Bind a IP:Porta
โ†“
3. Ascolta (loop infinito)
โ†“
4. Riceve messaggio
(recvfrom)
โ†“
5. Elabora richiesta
โ†“
6. Invia risposta
(sendto)

๐Ÿ’ป CLIENT

1. Crea socket UDP
โ†“
2. Prepara messaggio
โ†“
3. Invia al server
(sendto)
โ†“
4. Riceve risposta
(recvfrom)
โ†“
5. Elabora risposta
โ†“
6. Chiude socket

3.2 Ruoli e Responsabilitร 

๐Ÿ–ฅ๏ธ SERVER

  • Si mette in ascolto su un indirizzo/porta specifici
  • Attende messaggi dai client
  • Risponde alle richieste ricevute
  • รˆ sempre attivo (daemon)
  • Non inizia la comunicazione
  • Puรฒ gestire multiple richieste da client diversi

๐Ÿ’ป CLIENT

  • Inizia la comunicazione
  • Invia richieste al server
  • Attende risposte dal server
  • Si connette quando necessario
  • Non fa bind (porta assegnata automaticamente)
  • Puรฒ essere temporaneo (si chiude dopo uso)

๐Ÿ”ง 4. Socket UDP in Python

4.1 Il Modulo socket

Python fornisce il modulo socket built-in per la programmazione di rete. Non richiede installazioni esterne ed รจ disponibile su tutte le piattaforme.

# Importazione del modulo import socket

4.2 Creazione di una Socket UDP

Sintassi base per creare una socket UDP:
import socket # Creare una socket UDP sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
๐Ÿ’ก Parametri di socket.socket()
  • socket.AF_INET: Famiglia di indirizzi IPv4 (Address Family Internet)
  • socket.SOCK_DGRAM: Tipo datagram = UDP (DGRAM = datagram)
  • socket.SOCK_STREAM: Tipo stream = TCP (NON usare per UDP!)

4.3 Metodi Principali per UDP

4.3.1 bind(address) - Solo SERVER

Associa la socket a un indirizzo IP e porta specifici. Il server DEVE fare bind per "ascoltare" su una porta.

# Configurazione indirizzo server host = 'localhost' # o '127.0.0.1' o '0.0.0.0' port = 5000 # Scegli porta > 1024 # Bind della socket sock.bind((host, port)) # Nota: tupla (host, port)! print(f"Server in ascolto su {host}:{port}")
โš ๏ธ Note Importanti su bind()
  • Solo il server deve fare bind(), il client NO!
  • L'argomento รจ una tupla (host, port) con parentesi doppie
  • Usa porte > 1024 (le porte 0-1023 sono riservate)
  • '0.0.0.0' = ascolta su tutte le interfacce di rete
  • 'localhost' o '127.0.0.1' = solo connessioni locali

4.3.2 sendto(data, address) - CLIENT e SERVER

Invia dati a un destinatario specifico. Funziona sia per client che server.

# Preparazione messaggio messaggio = "Ciao Server!" # Conversione stringa in bytes bytes_messaggio = messaggio.encode('utf-8') # Invio al server sock.sendto(bytes_messaggio, ('localhost', 5000)) print(f"Inviato: {messaggio}")
๐Ÿ’ก Parametri di sendto()
  • data: dati da inviare come bytes (usa .encode())
  • address: tupla (host, port) del destinatario
  • Ritorna: numero di bytes inviati

4.3.3 recvfrom(bufsize) - CLIENT e SERVER

Riceve dati e l'indirizzo del mittente. Ritorna una tupla con i dati e l'indirizzo.

# Ricezione dati (max 1024 bytes) data, indirizzo_mittente = sock.recvfrom(1024) # Decodifica bytes in stringa messaggio = data.decode('utf-8') # Estrazione IP e porta del mittente ip_mittente, porta_mittente = indirizzo_mittente print(f"Ricevuto da {ip_mittente}:{porta_mittente}") print(f"Messaggio: {messaggio}")
๐Ÿ’ก Parametri e Ritorno di recvfrom()
  • bufsize: numero massimo di bytes da ricevere (tipicamente 1024 o 4096)
  • Ritorna tupla: (data, address)
  • data: bytes ricevuti
  • address: tupla (ip, porta) del mittente
  • Blocca l'esecuzione finchรฉ non arrivano dati

4.3.4 close() - Sempre!

Chiude la socket e libera le risorse. SEMPRE da chiamare quando non serve piรน.

# Chiusura socket sock.close() print("Socket chiusa")

4.4 Encoding e Decoding

โš ๏ธ CRITICO: Le Socket Lavorano con BYTES!

Le socket inviano e ricevono bytes, NON stringhe. Devi sempre convertire:

Conversione Stringa โ†” Bytes:
# Stringa โ†’ Bytes (prima di inviare) stringa = "Ciao" bytes_data = stringa.encode('utf-8') print(bytes_data) # b'Ciao' # Bytes โ†’ Stringa (dopo aver ricevuto) bytes_ricevuti = b'Ciao' stringa = bytes_ricevuti.decode('utf-8') print(stringa) # "Ciao" # Uso completo sock.sendto("Messaggio".encode('utf-8'), address) # Encode prima di inviare data, addr = sock.recvfrom(1024) messaggio = data.decode('utf-8') # Decode dopo aver ricevuto

๐Ÿ“ 5. Esempi Pratici Completi

5.1 Server UDP Echo - Versione Base

Server che rimanda indietro ogni messaggio ricevuto:
import socket # 1. Creazione socket UDP server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. Configurazione indirizzo e porta host = 'localhost' port = 5000 # 3. Bind: associa socket all'indirizzo server_socket.bind((host, port)) print(f"๐ŸŸข Server UDP in ascolto su {host}:{port}") # 4. Loop principale - server sempre attivo try: while True: # Ricevi dati dal client data, client_address = server_socket.recvfrom(1024) # Decodifica messaggio messaggio = data.decode('utf-8') print(f"๐Ÿ“จ Ricevuto da {client_address}: {messaggio}") # Prepara risposta (echo) risposta = f"Echo: {messaggio}" # Invia risposta al client server_socket.sendto(risposta.encode('utf-8'), client_address) print(f"๐Ÿ“ค Risposta inviata a {client_address}") except KeyboardInterrupt: print("\n๐Ÿ”ด Server terminato dall'utente") finally: # Chiudi sempre la socket! server_socket.close() print("๐Ÿ”’ Socket chiusa")

5.2 Client UDP - Versione Base

Client che invia un messaggio e riceve risposta:
import socket # 1. Creazione socket UDP client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. Indirizzo del server server_host = 'localhost' server_port = 5000 server_address = (server_host, server_port) # 3. Messaggio da inviare messaggio = "Ciao Server UDP!" try: # 4. Invia messaggio al server print(f"๐Ÿ“ค Invio messaggio a {server_address}") client_socket.sendto(messaggio.encode('utf-8'), server_address) print(f"โœ… Inviato: {messaggio}") # 5. Ricevi risposta dal server print("โณ Attendo risposta...") data, server_addr = client_socket.recvfrom(1024) # 6. Decodifica e mostra risposta risposta = data.decode('utf-8') print(f"๐Ÿ“จ Risposta dal server: {risposta}") except Exception as e: print(f"โŒ Errore: {e}") finally: # Chiudi socket client_socket.close() print("๐Ÿ”’ Socket chiusa")

5.3 Client Interattivo con Loop

Client che permette di inviare piรน messaggi:
import socket client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server_address = ('localhost', 5000) print("๐Ÿ’ฌ Client UDP Interattivo") print("Scrivi 'quit' per uscire\n") try: while True: # Input dall'utente messaggio = input("Messaggio > ") # Controlla comando di uscita if messaggio.lower() == 'quit': print("๐Ÿ‘‹ Arrivederci!") break # Invia messaggio client_socket.sendto(messaggio.encode('utf-8'), server_address) # Ricevi risposta data, _ = client_socket.recvfrom(1024) risposta = data.decode('utf-8') print(f"๐Ÿ“จ {risposta}\n") except KeyboardInterrupt: print("\n๐Ÿ‘‹ Interruzione utente") finally: client_socket.close() print("๐Ÿ”’ Connessione chiusa")

โšก 6. Differenze Chiave: Server vs Client

๐Ÿ–ฅ๏ธ SERVER UDP

# 1. Crea socket sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM ) # 2. BIND obbligatorio! sock.bind(('localhost', 5000)) # 3. Loop infinito while True: # Ricevi da qualsiasi client data, addr = sock.recvfrom(1024) # Processa e rispondi sock.sendto(risposta, addr)

๐Ÿ’ป CLIENT UDP

# 1. Crea socket sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM ) # 2. NO BIND necessario! # Porta assegnata automaticamente # 3. Invia al server server_addr = ('localhost', 5000) sock.sendto(messaggio, server_addr) # 4. Ricevi risposta data, _ = sock.recvfrom(1024) # 5. Chiudi quando finito sock.close()

๐ŸŽฎ 7. Demo Interattiva

๐ŸŽฒ Simulazione Comunicazione UDP

Clicca per vedere esempi di comunicazione client-server

๐Ÿ’ก 8. Concetti Avanzati

8.1 Gestione di Piรน Client

Server che traccia i client connessi:
import socket from datetime import datetime server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server_socket.bind(('localhost', 5000)) # Dizionario per tracciare client clients = {} # {address: {'count': n, 'last_seen': timestamp}} print("๐ŸŸข Server multi-client avviato") while True: data, client_addr = server_socket.recvfrom(1024) messaggio = data.decode('utf-8') # Aggiorna informazioni client if client_addr not in clients: clients[client_addr] = {'count': 0, 'last_seen': None} print(f"๐Ÿ†• Nuovo client: {client_addr}") clients[client_addr]['count'] += 1 clients[client_addr]['last_seen'] = datetime.now() # Prepara risposta con statistiche count = clients[client_addr]['count'] risposta = f"Messaggio #{count} ricevuto: {messaggio}" print(f"๐Ÿ“จ {client_addr} (msg #{count}): {messaggio}") print(f"๐Ÿ“Š Client attivi: {len(clients)}") server_socket.sendto(risposta.encode('utf-8'), client_addr)

8.2 Timeout e Gestione Errori

Client con timeout per evitare blocchi infiniti:
import socket client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Imposta timeout di 5 secondi client_socket.settimeout(5.0) server_address = ('localhost', 5000) try: # Invia messaggio messaggio = "Test con timeout" client_socket.sendto(messaggio.encode('utf-8'), server_address) print("๐Ÿ“ค Messaggio inviato, attendo risposta...") # Attende risposta con timeout data, server = client_socket.recvfrom(1024) risposta = data.decode('utf-8') print(f"โœ… Risposta ricevuta: {risposta}") except socket.timeout: print("โฐ Timeout! Server non risponde") except ConnectionRefusedError: print("โŒ Connessione rifiutata - server non attivo?") except Exception as e: print(f"โŒ Errore: {e}") finally: client_socket.close()

8.3 Invio di Dati Strutturati (JSON)

Invio e ricezione di dati complessi:
import socket import json # ===== CLIENT ===== client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Dati strutturati come dizionario dati = { 'comando': 'calcola', 'operazione': 'somma', 'numeri': [10, 20, 30], 'timestamp': '2025-10-09' } # Serializza in JSON e converti in bytes json_data = json.dumps(dati) client_socket.sendto(json_data.encode('utf-8'), ('localhost', 5000)) # Ricevi risposta data, _ = client_socket.recvfrom(1024) risposta = json.loads(data.decode('utf-8')) print(f"Risultato: {risposta['risultato']}") # ===== SERVER ===== server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server_socket.bind(('localhost', 5000)) while True: data, client_addr = server_socket.recvfrom(1024) # Deserializza JSON richiesta = json.loads(data.decode('utf-8')) # Elabora if richiesta['operazione'] == 'somma': risultato = sum(richiesta['numeri']) # Prepara risposta risposta = {'risultato': risultato, 'status': 'ok'} # Invia risposta serializzata json_risposta = json.dumps(risposta) server_socket.sendto(json_risposta.encode('utf-8'), client_addr)

โš ๏ธ 9. Problemi Comuni e Soluzioni

โŒ Errore: "Address already in use"

Causa: La porta รจ giร  occupata da un altro processo o da una precedente istanza non chiusa.

Soluzioni:

  • Cambia numero di porta
  • Attendi 1-2 minuti che il sistema rilasci la porta
  • Trova e termina il processo che usa la porta
  • Usa sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
โŒ Errore: "Connection refused"

Causa: Il server non รจ in ascolto sulla porta specificata.

Soluzioni:

  • Verifica che il server sia avviato PRIMA del client
  • Controlla che IP e porta siano corretti
  • Verifica firewall non blocchi la porta
โŒ Errore: "a bytes-like object is required"

Causa: Tentativo di inviare una stringa invece di bytes.

Soluzione:

# โŒ SBAGLIATO sock.sendto("messaggio", address) # โœ… CORRETTO sock.sendto("messaggio".encode('utf-8'), address)
โŒ Client si blocca su recvfrom()

Causa: recvfrom() blocca l'esecuzione finchรฉ non arrivano dati.

Soluzioni:

  • Usa sock.settimeout(secondi) per impostare un timeout
  • Assicurati che il server risponda
  • Gestisci l'eccezione socket.timeout

โœ… 10. Best Practices

โœ… Buone Pratiche per Socket UDP
  1. Sempre chiudere le socket:
    try: # usa la socket sock.sendto(data, addr) finally: sock.close() # Garantito!
  2. Gestire le eccezioni:
    try: data, addr = sock.recvfrom(1024) except socket.timeout: print("Timeout") except Exception as e: print(f"Errore: {e}")
  3. Usare porte > 1024 (0-1023 sono riservate)
  4. Impostare timeout per evitare blocchi infiniti
  5. Validare i dati ricevuti prima di elaborarli
  6. Usare buffer size appropriato (1024, 4096, 8192 bytes)
  7. Loggare attivitร  per debugging

๐Ÿ“Š 11. Riepilogo Veloce

๐ŸŽฏ Checklist Socket UDP
Passo Server Client
1. Import import socket
2. Crea Socket socket.socket(AF_INET, SOCK_DGRAM)
3. Bind โœ… sock.bind((host, port)) โŒ Non necessario
4. Loop โœ… while True: โŒ Opzionale
5. Ricevi data, addr = recvfrom(1024) data, _ = recvfrom(1024)
6. Invia sendto(data, addr) sendto(data, server_addr)
7. Chiudi โœ… sock.close()

๐ŸŽ“ 12. Esercizi Preparatori

โญ Esercizio 1: Echo Server

Difficoltร : Facile

Obiettivo: Crea un server che riceve un messaggio e lo rimanda indietro uguale.

Cosa imparare:

  • Creazione socket UDP
  • Uso di bind(), recvfrom(), sendto()
  • Encoding/decoding
โญโญ Esercizio 2: Calcolatrice Remota

Difficoltร : Medio

Obiettivo: Il client invia operazioni ("5+3"), il server calcola e risponde.

Cosa imparare:

  • Parsing dei dati ricevuti
  • Validazione input
  • Gestione errori (divisione per zero)
โญโญ Esercizio 3: Server Multi-Richiesta

Difficoltร : Medio

Obiettivo: Server che tiene traccia di quanti messaggi ha ricevuto da ogni client.

Cosa imparare:

  • Gestione stato con dizionari
  • Tracking client multipli
  • Invio statistiche personalizzate

๐Ÿ“š 13. Risorse Aggiuntive

๐Ÿ”— Link Utili
  • Documentazione Python socket: docs.python.org/3/library/socket.html
  • RFC 768 - UDP Protocol: Specifica ufficiale del protocollo UDP
  • Wireshark: Tool per analizzare traffico di rete UDP
  • netcat (nc): Utility command-line per testare socket
๐Ÿ› ๏ธ Comandi Utili
# Linux/Mac: Vedere porte in uso netstat -tulpn | grep :5000 # Testare server con netcat echo "test" | nc -u localhost 5000 # Windows: Vedere porte netstat -ano | findstr :5000

โœ… 14. Checklist Finale

๐Ÿ“‹ Prima dell'Esercitazione
  • โ˜ So cos'รจ una socket
  • โ˜ Conosco la differenza tra TCP e UDP
  • โ˜ Capisco quando usare UDP
  • โ˜ So che SOCK_DGRAM = UDP
  • โ˜ So che SOCK_STREAM = TCP
  • โ˜ So cosa fanno bind(), sendto(), recvfrom()
  • โ˜ Ricordo che devo usare encode()/decode()
  • โ˜ Capisco la differenza tra client e server
  • โ˜ So che il server fa bind(), il client no
  • โ˜ So impostare un timeout
  • โ˜ So gestire le eccezioni base
  • โ˜ Ho provato gli esempi pratici
  • โ˜ Ho completato almeno 1 esercizio preparatorio

๐ŸŽฏ 15. Confronto Visivo: Flusso Completo

COMUNICAZIONE UDP COMPLETA FASE 1: INIZIALIZZAZIONE Server Client โ”‚ โ”‚ โ”œโ”€ socket() โ”œโ”€ socket() โ”œโ”€ bind(port) โ”‚ (no bind!) โ””โ”€ while True: โ”€โ”€โ”€โ” โ””โ”€ pronto โ”‚ FASE 2: COMUNICAZIONE Server โ”‚ Client โ”‚ โ”‚ โ”‚ โ”œโ”€ recvfrom() โ—„โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค sendto() โ”‚ (attende...) โ”‚ โ”‚ (invia msg) โ”‚ โ”‚ โ”‚ โ”œโ”€ elabora() โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€ sendto() โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”œโ”€ recvfrom() โ”‚ โ”‚ โ”‚ (riceve) โ”‚ โ”‚ โ”‚ โ””โ”€โ—„โ”€โ”€โ”€loopโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€ close() CARATTERISTICHE: โœ“ No handshake (diverso da TCP) โœ“ Ogni messaggio รจ indipendente โœ“ Server puรฒ rispondere a piรน client โœ— Nessuna garanzia di consegna โœ— Nessun controllo ordine pacchetti

๐Ÿ” 16. Debug e Troubleshooting

16.1 Logging Dettagliato

Server con logging completo per debugging:
import socket import logging from datetime import datetime # Configura logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s' ) def server_con_debug(): # Creazione socket logging.info("๐Ÿ”ง Creazione socket UDP...") server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Configurazione host = 'localhost' port = 5000 logging.info(f"โš™๏ธ Configurazione: {host}:{port}") # Bind try: server_socket.bind((host, port)) logging.info(f"โœ… Bind riuscito su {host}:{port}") except OSError as e: logging.error(f"โŒ Errore bind: {e}") return logging.info("๐ŸŸข Server in ascolto...") try: while True: logging.debug("โณ Attendo messaggi...") # Ricezione data, client_addr = server_socket.recvfrom(1024) logging.info(f"๐Ÿ“จ Ricevuto {len(data)} bytes da {client_addr}") # Decodifica try: messaggio = data.decode('utf-8') logging.debug(f"๐Ÿ“ Messaggio decodificato: '{messaggio}'") except UnicodeDecodeError as e: logging.error(f"โŒ Errore decodifica: {e}") continue # Elaborazione risposta = f"Echo: {messaggio}" logging.debug(f"๐Ÿ”„ Preparata risposta: '{risposta}'") # Invio bytes_inviati = server_socket.sendto( risposta.encode('utf-8'), client_addr ) logging.info(f"๐Ÿ“ค Inviati {bytes_inviati} bytes a {client_addr}") except KeyboardInterrupt: logging.warning("โš ๏ธ Interruzione da tastiera") finally: server_socket.close() logging.info("๐Ÿ”’ Socket chiusa correttamente") if __name__ == "__main__": server_con_debug()

16.2 Test con Wireshark

๐Ÿ”ฌ Analisi Traffico UDP con Wireshark

Come usare Wireshark per vedere i pacchetti UDP:

  1. Installa Wireshark dal sito ufficiale
  2. Avvia Wireshark e seleziona interfaccia (es: Loopback per localhost)
  3. Applica filtro: udp.port == 5000
  4. Avvia il tuo server Python
  5. Invia messaggi dal client
  6. Osserva i pacchetti catturati in Wireshark

Cosa vedrai:

  • IP sorgente e destinazione
  • Porte sorgente e destinazione
  • Dimensione pacchetto
  • Payload (contenuto del messaggio)
  • Timestamp esatto di invio/ricezione

๐Ÿš€ 17. Esempi Avanzati

17.1 Server con Comandi

Server che esegue comandi specifici:
import socket from datetime import datetime server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server_socket.bind(('localhost', 5000)) print("๐ŸŸข Server comandi avviato") print("Comandi disponibili: TIME, ECHO, INFO, QUIT") while True: data, client_addr = server_socket.recvfrom(1024) comando = data.decode('utf-8').strip().upper() print(f"๐Ÿ“จ {client_addr}: {comando}") # Gestione comandi if comando == 'TIME': risposta = f"Ora server: {datetime.now().strftime('%H:%M:%S')}" elif comando.startswith('ECHO '): messaggio = comando[5:] # Rimuove "ECHO " risposta = f"Echo: {messaggio}" elif comando == 'INFO': risposta = f"Server UDP v1.0 - Porta: 5000" elif comando == 'QUIT': risposta = "Arrivederci!" server_socket.sendto(risposta.encode('utf-8'), client_addr) print(f"๐Ÿ‘‹ Client {client_addr} disconnesso") continue else: risposta = f"Comando sconosciuto: {comando}" # Invia risposta server_socket.sendto(risposta.encode('utf-8'), client_addr) print(f"๐Ÿ“ค Risposta: {risposta}")

17.2 Client con Retry Automatico

Client che riprova automaticamente in caso di timeout:
import socket import time def invia_con_retry(messaggio, max_tentativi=3, timeout=2): """Invia messaggio con retry automatico""" client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) client_socket.settimeout(timeout) server_address = ('localhost', 5000) for tentativo in range(1, max_tentativi + 1): try: print(f"๐Ÿ”„ Tentativo {tentativo}/{max_tentativi}") # Invia messaggio client_socket.sendto(messaggio.encode('utf-8'), server_address) print(f"๐Ÿ“ค Inviato: {messaggio}") # Attendi risposta con timeout data, server = client_socket.recvfrom(1024) risposta = data.decode('utf-8') print(f"โœ… Risposta ricevuta: {risposta}") client_socket.close() return risposta except socket.timeout: print(f"โฐ Timeout al tentativo {tentativo}") if tentativo < max_tentativi: print(f"โณ Attendo {timeout} secondi prima di riprovare...") time.sleep(timeout) else: print("โŒ Tutti i tentativi falliti") except Exception as e: print(f"โŒ Errore: {e}") break client_socket.close() return None # Uso risposta = invia_con_retry("Messaggio importante", max_tentativi=5) if risposta: print(f"โœ… Operazione riuscita: {risposta}") else: print("โŒ Operazione fallita")

๐Ÿ’ป 18. Progetto Completo: Chat UDP

Sistema di chat semplice con broadcast:
import socket from datetime import datetime # ===== SERVER CHAT ===== class ChatServer: def __init__(self, host='localhost', port=5000): self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.bind((host, port)) self.clients = {} # {address: username} print(f"๐Ÿ’ฌ Server chat avviato su {host}:{port}") def broadcast(self, messaggio, esclude=None): """Invia messaggio a tutti i client tranne esclude""" for client_addr in self.clients: if client_addr != esclude: self.socket.sendto( messaggio.encode('utf-8'), client_addr ) def avvia(self): print("โณ In attesa di client...") while True: data, client_addr = self.socket.recvfrom(1024) messaggio = data.decode('utf-8') # Comando JOIN if messaggio.startswith('JOIN:'): username = messaggio.split(':')[1] self.clients[client_addr] = username print(f"โœ… {username} entrato ({client_addr})") # Notifica altri utenti notifica = f"[SERVER] {username} รจ entrato nella chat" self.broadcast(notifica, esclude=client_addr) # Messaggio benvenuto benvenuto = f"[SERVER] Benvenuto {username}! Utenti: {len(self.clients)}" self.socket.sendto(benvenuto.encode('utf-8'), client_addr) # Comando QUIT elif messaggio == 'QUIT': if client_addr in self.clients: username = self.clients[client_addr] del self.clients[client_addr] print(f"๐Ÿ‘‹ {username} uscito") # Notifica uscita notifica = f"[SERVER] {username} ha lasciato la chat" self.broadcast(notifica) # Messaggio normale else: if client_addr in self.clients: username = self.clients[client_addr] timestamp = datetime.now().strftime('%H:%M:%S') # Formatta messaggio msg_formattato = f"[{timestamp}] {username}: {messaggio}" print(msg_formattato) # Broadcast a tutti self.broadcast(msg_formattato, esclude=client_addr) # Conferma al mittente conferma = f"[TU] {messaggio}" self.socket.sendto(conferma.encode('utf-8'), client_addr) # Avvia server if __name__ == "__main__": server = ChatServer() try: server.avvia() except KeyboardInterrupt: print("\n๐Ÿ”ด Server terminato")

๐ŸŽ“ 19. Conclusione

๐ŸŽฏ Cosa Hai Imparato
  • โœ… Cos'รจ una socket e come funziona
  • โœ… Differenze fondamentali tra TCP e UDP
  • โœ… Quando usare UDP invece di TCP
  • โœ… Architettura client-server
  • โœ… Creare socket UDP con Python
  • โœ… Metodi principali: bind(), sendto(), recvfrom()
  • โœ… Encoding e decoding di stringhe
  • โœ… Gestione errori e timeout
  • โœ… Pattern avanzati (retry, logging, comandi)
  • โœ… Best practices per socket UDP
๐Ÿ“š Prossimi Passi
  1. โœ… Completa gli esercizi preparatori
  2. โœ… Prova gli esempi sul tuo computer
  3. โœ… Sperimenta con Wireshark
  4. โœ… Modifica gli esempi per capire meglio
  5. โžก๏ธ Sei pronto per l'esercitazione valutata!
๐Ÿ’ก Consiglio Finale: La chiave per padroneggiare le socket UDP รจ la pratica. Prova a creare piccoli progetti personali, sperimenta con diversi scenari, e non aver paura di fare errori - sono la migliore opportunitร  di apprendimento!

Buono studio e buon coding! ๐Ÿš€โœจ